// ~ Package Declaration
// ==================================================

package com.wms.data.helper;

import com.wms.data.DataException;
import com.wms.data.dao.Dao;
import com.wms.data.pojo.Pojo;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;


// ~ Comments
// ==================================================

/**
 * 
 * 相關數據操作的一個helper class.
 * 
 * <pre>
 * 歷史紀錄：
 * 2008/11/14 Timothy
 * 	新建檔案
 *  該類別的應以 DI 的方式產生實例并注入數據
 * 2010/2/3 Timothy
 *  實現 {@link ApplicationContextAware} 來構造 Dao Container
 * </pre>
 * 
 * @author <pre>
 * SD
 * 	Timothy
 * PG
 * 
 * UT
 * 
 * MA
 * </pre>
 * @version $Rev$
 * 
 *          <p/>
 *          $Id$
 * 
 */
public class DataHelper implements ApplicationContextAware {

	// ~ Static Fields
	// ==================================================

	// ~ Fields
	// ==================================================

	private ApplicationContext applicationContext;

	/**
	 * 2008/12/4 Timothy 宣告一個 pojoClass 映射 相應 Dao 的 map
	 */
	private Map<Class<? extends Pojo<?>>, Dao<Pojo<?>, ?>> pojoMappingDao;

	// ~ Constructors
	// ==================================================

	// ~ Methods
	// ==================================================

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * 	根據 pojo's class 獲取 對應的 Dao's instance
	 * </pre>
	 * 
	 * @see com.wms.data.helper.DataHelper#getDao(java.lang.Class)
	 */
	@SuppressWarnings("unchecked")
	public final <D extends Dao<P, PK>, P extends Pojo<PK>, PK extends Serializable> D getDao(Class<P> pojoClass) {

		// 當獲尚未管理dao bean時，將 spring 管理的 dao 設定到 pojoMappingDao
		if (this.pojoMappingDao == null) {

			this.constructDaoContainer();
		}

		// 考慮可能出現 Sub Class 的狀況，所以需要遞歸再次查找其 superclass 來獲取 Dao instance
		D dao = (D) this.getDaoInstance(pojoClass);

		// 當獲取不到 dao 時
		if (dao == null) {

			throw new DataException(new IllegalArgumentException(String
				.format("此[%s]對應的Dao沒有實現、或沒有註冊為Spring Bean，請查看相關設定！！！")));
		}

		return dao;
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * 	考慮 pojoClass 可能出現 Sub Class 的狀況，所以需要遞歸再次查找其 superclass 來獲取 Dao instance
	 * </pre>
	 * 
	 * @param pojoClass
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Dao getDaoInstance(Class pojoClass) {

		Dao dao = this.pojoMappingDao.get(pojoClass);

		if (dao == null) {

			dao = this.getDaoInstance(pojoClass.getSuperclass());

			// 當查找到對應的 dao 時，就將此 pojoClass 與 dao 的映射加入 pojoMappingDao
			if (dao != null) {
				this.pojoMappingDao.put(pojoClass, dao);
			}
		}

		return dao;
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * 	構造 dao 容器，使用 pojoClass 映射對應的 Dao instance.
	 * </pre>
	 * 
	 */
	@SuppressWarnings("unchecked")
	private void constructDaoContainer() {

		this.pojoMappingDao = new HashMap<Class<? extends Pojo<?>>, Dao<Pojo<?>, ?>>();

		// 將 spring 管理的 dao 設定到 pojoMappingDao
		for (String name : this.getApplicationContext().getBeanDefinitionNames()) {

			Object bean = this.getApplicationContext().getBean(name);

			// 只存取Dao物件
			if (bean instanceof Dao) {

				this.pojoMappingDao.put(((Dao) bean).getPojoClass(),
					(Dao<Pojo<? extends Serializable>, ? extends Serializable>) bean);
			}
		}
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * 	根據 pojo's instance 獲取 對應的 Dao's instance
	 * </pre>
	 * 
	 * @see com.wms.data.helper.DataHelper#getDao(com.vs.data.pojo.Pojo)
	 */
	@SuppressWarnings("unchecked")
	public final <D extends Dao<P, PK>, P extends Pojo<PK>, PK extends Serializable> D getDao(P pojo) {

		if (pojo == null) {
			throw new DataException(new NullPointerException("pojo 不可為 null!!!"));
		}

		return (D) this.getDao(pojo.getClass());
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 *   Create a pojo.
	 * </pre>
	 * 
	 * @param <P>
	 * @param <PK>
	 * @param pojo
	 */
	public final <P extends Pojo<PK>, PK extends Serializable> void create(P pojo) {

		this.getDao(pojo).create(pojo);
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * 	Update a pojo by id.
	 * </pre>
	 * 
	 * @param <P>
	 * @param <PK>
	 * @param pojo
	 */
	public final <P extends Pojo<PK>, PK extends Serializable> void update(P pojo) {

		this.getDao(pojo).update(pojo);
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * 	Delete a pojo by id.
	 * </pre>
	 * 
	 * @param <P>
	 * @param <PK>
	 * @param pojo
	 */
	public final <P extends Pojo<PK>, PK extends Serializable> void delete(P pojo) {

		this.getDao(pojo).delete(pojo);
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * 	Delete a pojo by id.
	 * </pre>
	 * 
	 * @param <P>
	 * @param <PK>
	 * @param pojoClass
	 * @param id
	 */
	public final <P extends Pojo<PK>, PK extends Serializable> void delete(Class<P> pojoClass, PK id) {

		this.getDao(pojoClass).delete(id);
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * 	Find pojo by id, if didn't query the pojo, return null;
	 * </pre>
	 * 
	 * @param <P>
	 * @param <PK>
	 * @param pojoClass
	 * @param id
	 * @return
	 */
	public final <P extends Pojo<PK>, PK extends Serializable> P findById(Class<P> pojoClass, PK id) {

		return this.getDao(pojoClass).findById(id);
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * 	Find pojo by id, if didn't query the pojo, return null;
	 * </pre>
	 * 
	 * @param <P>
	 * @param <PK>
	 * @param pojo
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public final <P extends Pojo<PK>, PK extends Serializable> P findById(P pojo) {

		return (P) this.getDao(pojo.getClass()).findById(pojo.getId());
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * 	Find all pojos
	 * </pre>
	 * 
	 * @param <P>
	 * @param <PK>
	 * @param pojoClass
	 * @return
	 */
	public final <P extends Pojo<PK>, PK extends Serializable> List<P> findAll(Class<P> pojoClass) {

		return this.getDao(pojoClass).findAll();
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 *   Check exists pojo by id
	 * </pre>
	 * 
	 * @param <P>
	 * @param <PK>
	 * @param pojoClass
	 * @param id
	 * @return
	 */
	public <P extends Pojo<PK>, PK extends Serializable> boolean exists(Class<P> pojoClass, PK id) {

		return this.getDao(pojoClass).exists(id);
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 *   Check exists pojo by id
	 * </pre>
	 * 
	 * @param <P>
	 * @param <PK>
	 * @param pojo
	 * @return
	 */
	public <P extends Pojo<PK>, PK extends Serializable> boolean exists(P pojo) {

		return this.getDao(pojo).exists(pojo.getId());
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 *   Check not exists pojo by id
	 * </pre>
	 * 
	 * @param <P>
	 * @param <PK>
	 * @param pojoClass
	 * @param id
	 * @return
	 */
	public <P extends Pojo<PK>, PK extends Serializable> boolean notExists(Class<P> pojoClass, PK id) {

		return this.getDao(pojoClass).notExists(id);
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy 
	 *   Check not exists pojo by id
	 * </pre>
	 * 
	 * @param <P>
	 * @param <PK>
	 * @param pojo
	 * @return
	 */
	public <P extends Pojo<PK>, PK extends Serializable> boolean notExists(P pojo) {

		return this.getDao(pojo).notExists(pojo.getId());
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * </pre>
	 * 
	 * @return
	 */
	public ApplicationContext getApplicationContext() {

		return this.applicationContext;
	}

	/**
	 * <pre>
	 * 2010-2-3 Timothy
	 * </pre>
	 * 
	 * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
	 */
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

		this.applicationContext = applicationContext;
	}
}
